package com.coincalf.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.coincalf.dto.*;
import com.coincalf.entity.*;
import com.coincalf.framework.constants.Constant;
import com.coincalf.framework.dto.ApplyWithdrawDTO;
import com.coincalf.framework.dto.CoinWithdrawDTO;
import com.coincalf.framework.dto.ForexMarketDTO;
import com.coincalf.framework.enums.*;
import com.coincalf.framework.exception.AccountException;
import com.coincalf.framework.exception.GlobalDefaultException;
import com.coincalf.framework.http.Response;
import com.coincalf.framework.utils.DateUtil;
import com.coincalf.mapper.AccountMapper;
import com.coincalf.service.*;
import com.coincalf.wallet.feign.CoinWithdrawServiceClient;
import com.coincalf.wallet.feign.UserAddressServiceClient;
import com.huobi.client.MarketClient;
import com.huobi.client.req.market.CandlestickRequest;
import com.huobi.constant.HuobiOptions;
import com.huobi.constant.enums.CandlestickIntervalEnum;
import com.huobi.model.market.Candlestick;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 用户财产记录 服务实现类
 * </p>
 *
 * @author qiang
 * @since 2018-05-12
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService, Constant {

    @Autowired
    private AccountDetailService accountDetailService;
    @Autowired
    private MarketService marketService;
    @Autowired
    private CoinService coinService;
    @Autowired
    private ConfigService configService;
    @Autowired
    private ForexOpenPositionOrderService forexOpenPositionOrderService;
    @Autowired
    private UserAddressServiceClient addressPoolServiceClient;
    @Autowired
    private CoinWithdrawServiceClient coinWithdrawServiceClient;
    @Autowired
    private UserWalletService userWalletService;
    @Autowired
    private ForexAccountService forexAccountService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private SmsService smsService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 根据用户ID和币种名称查询资金账户
     *
     * @param userId   用户ID
     * @param coinName 币种名称
     * @return
     */
    @Override
    public Account selectByUserAndCoinName(long userId, String coinName) {
        return baseMapper.selectByUserAndCoinName(userId, coinName);
    }

    /**
     * 根据用户ID和币种名称查询资金账户
     *
     * @param userId 用户ID
     * @param coinId 币种ID
     * @return
     */
    @Override
    public Account queryByUserIdAndCoinId(long userId, long coinId) {
        EntityWrapper<Account> wrapper = new EntityWrapper<>();
        wrapper.eq("user_id", userId)
                .eq("coin_id", coinId)
                .eq("status", BaseStatus.EFFECTIVE.getCode())
                .last("LIMIT 1");
        List<Account> accountList = baseMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(accountList)) {
            return null;
        }
        return accountList.get(0);
    }

    /**
     * 冻结资金
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param orderId      关联订单号,提币纪录表主键id
     * @return
     */
    @Override
    public boolean lockAmount(long userId,
                              long coinId,
                              BigDecimal amount,
                              BusinessType businessType,
                              long orderId
    ) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("冻结资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new AccountException("资金账户异常");
        }
        if (baseMapper.lockAmount(account.getId(), amount) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.OUT.getType(),
                    businessType.getCode(),
                    amount,
                    "冻结");
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("冻结资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new AccountException("可用资金不足");
    }

    /**
     * 解冻资金
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param orderId      关联订单号
     * @return
     */
    @Override
    public boolean unlockAmount(long userId,
                                long coinId,
                                BigDecimal amount,
                                BusinessType businessType,
                                long orderId) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("解冻资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new AccountException("资金账户异常");
        }
        if (baseMapper.unlockAmount(account.getId(), amount) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.INCOME.getType(),
                    businessType.getCode(),
                    amount,
                    "解冻");
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("解冻资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new AccountException("解冻资金失败");
    }

    /**
     * 增加资金
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param remark       备注
     * @param orderId      关联订单号
     * @return
     */
    @Override
    public boolean addAmount(long userId,
                             long coinId,
                             BigDecimal amount,
                             BusinessType businessType,
                             String remark,
                             long orderId) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("增加资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new com.coincalf.framework.exception.AccountException("资金账户异常");
        }
        if (baseMapper.addAmount(account.getId(), amount) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(
                    userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.INCOME.getType(),
                    businessType.getCode(),
                    amount,
                    remark
            );
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("增加资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new com.coincalf.framework.exception.AccountException("增加资金失败");
    }

    @Override
    public Page<Account> selectAccountByNames(Page<Account> page, long coinId, String userName, long agencyUserId, String createTime, long parentId) {
        List<Account> list = baseMapper.selectAccountByNames(page, coinId, userName, agencyUserId, createTime, parentId);
        return page.setRecords(list);
    }

    @Override
    public List<Map<String, Object>> selectAllLevelUser(long agencyUserId) {
        return baseMapper.selectAllLevelUser(agencyUserId);
    }

    @Override
    public ResultDTO selectAccountTotal(long anencyUserId) {
        Map<String, Object> mapUsdt = baseMapper.selectUsdtAccountTotal(anencyUserId);
        Map<String, Object> mapBtc = baseMapper.selectBtcAccountTotal(anencyUserId);
        mapUsdt.putAll(mapBtc);
        return ResultDTO.successResult(mapUsdt);
    }

    /**
     * 查询统计实时资金账户
     *
     * @param userId 用户ID
     * @param coinId 币种ID
     * @return
     */
    @Override
    public ForexAssetDTO forexAsset(long userId, long coinId) throws GlobalDefaultException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            throw new GlobalDefaultException(2023);
        }
        ForexAssetDTO forexAsset = new ForexAssetDTO();
        // 浮动盈亏
        BigDecimal floatProfit = BigDecimal.ZERO;
        // 总盈亏
        BigDecimal totalProfit = account.getTotalProfit();
        EntityWrapper<ForexOpenPositionOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("user_id", userId).eq("coin_id", coinId);
        List<ForexOpenPositionOrder> forexOpenPositionOrders = forexOpenPositionOrderService.selectList(wrapper);
        if (!CollectionUtils.isEmpty(forexOpenPositionOrders)) {
            for (ForexOpenPositionOrder openPositionOrder : forexOpenPositionOrders) {
                Long marketId = openPositionOrder.getMarketId();
                ForexMarketDTO forexMarket = marketService.getMarketDataById(marketId);
                Market market = marketService.getMarketById(marketId);
                if (openPositionOrder.getType() == OrderType.BUY.getCode()) {
                    // 多头持仓浮动盈亏 = 买一价 - 持仓买入价） * 合约单位 * 报价货币兑（USDT或BTC） * 手数
                    floatProfit = floatProfit.add(forexMarket.getBidPrice()
                            .subtract(openPositionOrder.getPrice())
                            .multiply(new BigDecimal(forexMarket.getContractUnit()))
                            .multiply(marketService.getPriceToCoinRate(market))
                            .multiply(openPositionOrder.getNum().subtract(openPositionOrder.getCloseNum())));
                } else {
                    // 空头持仓浮动盈亏 = 卖一价 - 持仓买入价） * 合约单位 * 报价货币兑（USDT或BTC） * 手数
                    floatProfit = floatProfit.add(openPositionOrder.getPrice()
                            .subtract(forexMarket.getAskPrice())
                            .multiply(new BigDecimal(market.getContractUnit()))
                            .multiply(marketService.getPriceToCoinRate(market))
                            .multiply(openPositionOrder.getNum().subtract(openPositionOrder.getCloseNum())));


                }
            }
        }
        forexAsset.setTotalAmount(account.getBalanceAmount().add(account.getFreezeAmount()).add(account.getLockMargin()))
                .setFloatProfit(floatProfit.setScale(8, BigDecimal.ROUND_HALF_UP)) // 持仓盈亏/浮动盈亏
                .setTotalProfit(totalProfit.setScale(8, BigDecimal.ROUND_HALF_UP)) // 总盈亏
                .setLockMargin(account.getLockMargin())                                     // 占用保证金
                .setBalanceAmount(account.getBalanceAmount());                              // 可用保证金
        return forexAsset;
    }

    /**
     * 币币交易用户交易对账户资产
     *
     * @param symbol 交易对标识符
     * @param userId 用户ID
     */
    @Override
    public SymbolAssetDTO queryAccount(String symbol, long userId) {
        Market market = marketService.queryBySymbol(symbol);
        long priceCoinId = market.getBuyCoinId();
        long baseCoinId = market.getSellCoinId();
        Coin priceCoin = coinService.queryById(priceCoinId);
        Coin baseCoin = coinService.queryById(baseCoinId);
        Account priceCoinAccount = this.queryByUserIdAndCoinId(userId, priceCoinId);
        Account baseCoinAccount = this.queryByUserIdAndCoinId(userId, baseCoinId);
        SymbolAssetDTO symbolAsset = new SymbolAssetDTO();
        symbolAsset.setBuyAmount(priceCoinAccount.getBalanceAmount())
                .setBuyLockAmount(priceCoinAccount.getFreezeAmount().add(priceCoinAccount.getLockMargin()))
                .setBuyFeeRate(market.getFeeBuy())
                .setBuyUnit(priceCoin.getName())
                .setSellAmount(baseCoinAccount.getBalanceAmount())
                .setSellLockAmount(baseCoinAccount.getFreezeAmount().add(baseCoinAccount.getLockMargin()))
                .setSellFeeRate(market.getFeeSell())
                .setSellUnit(baseCoin.getName());
        return symbolAsset;
    }

    /**
     * 获取钱包充值地址
     *
     * @param coinId 币种ID
     * @param userId 用户ID
     * @return
     */
    @Override
    @Transactional
    public Map<String, String> queryRechargeAddress(Long coinId, Long userId) {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("请求参数错误");
            throw new GlobalDefaultException(10001);
        }
        if (!StringUtils.isEmpty(account.getRecAddr())) {
            Map<String, String> map = new HashMap<>(1);
            map.put("address", account.getRecAddr());
            return map;
        }
        Coin coin = coinService.queryById(coinId);
        Long mainCoinId = null;
        if (!coin.getIsMain()) {
            mainCoinId = coin.getMainCoinId();
        }
        /*// 如果是token币则获取主链钱包地址
        String mainCoinType = coin.getType();
        if (coin.getType().toLowerCase().endsWith(TOKEN)) {
            mainCoinType = mainCoinType.substring(0, mainCoinType.length() - TOKEN.length());
            Coin mainCoin = coinService.queryByType(mainCoinType);
            if (mainCoin != null) {
                mainCoinId = mainCoin.getId();
            }
        }*/
        // 调用钱包服务获取充值地址
        Map<String, String> address = addressPoolServiceClient.getAddress(userId, coinId, mainCoinId);
        if (StringUtils.isEmpty(address)) {
            log.error("获取钱包充值地址失败");
            throw new GlobalDefaultException(50024);
        }
        baseMapper.setRechargeAddress(account.getId(), address.get("address"));
        return address;
    }

    /**
     * 申请提币
     *
     * @param applyWithdraw 申请提币参数
     * @param user          用户
     */
    @Override
    public Response applyWithdraw(ApplyWithdrawDTO applyWithdraw, User user) {
        DecimalFormat format = new DecimalFormat("#.########");
        // 验证短信验证码
        Sms sms = new Sms();
        sms.setMobile(user.getMobile());
        sms.setContent(applyWithdraw.getVerifyCode());
        Response response = smsService.verifySms(sms);
        if (!response.getErrmsg().equalsIgnoreCase("ok")) {
            return response;
        }
        /*if (user.getAuthStatus() == null || user.getAuthStatus() != 2) {
            log.error("用户尚未完成高级实名认证");
            throw new GlobalDefaultException(50029);
        }*/
        // 校验资金密码
        /*if (!new BCryptPasswordEncoder().matches(applyWithdraw.getPayPassword(), user.getPaypassword())) {
            log.error("资金密码错误");
            throw new GlobalDefaultException(2012);
        }*/
        Coin coin = coinService.queryById(applyWithdraw.getCoinId());
        if (coin == null) {
            log.error("提现币种错误");
            throw new GlobalDefaultException(-2);
        }
        BigDecimal applyAmount = applyWithdraw.getAmount();
        // day_max_amount 当日最大提现数量
        String drawKey = "APPLY_WITHDRAW:" + user.getId();
        BigDecimal dayMaxAmount = applyAmount;
        String dayMaxAmountString = stringRedisTemplate.opsForValue().get(drawKey);
        if (!StringUtils.isEmpty(dayMaxAmountString)) {
            dayMaxAmount = new BigDecimal(dayMaxAmountString).add(applyAmount);
        }
        if (dayMaxAmount.compareTo(coin.getDayMaxAmount()) > 0) {
            log.error("当天提现总额不能大于当天最大提现数量");
            //throw new GlobalDefaultException(20012);
            return Response.err(20012, "超出单日提现上限：" + format.format(coin.getDayMaxAmount()) + coin.getName());
        }
        // max_amount 单笔最大提现数量
        if (applyAmount.compareTo(coin.getMaxAmount()) > 0) {
            log.error("提现数量不能大于单笔最大提现数量");
            //throw new GlobalDefaultException(20013);
            return Response.err(20013, "超出单笔提现上限" + format.format(coin.getMaxAmount()) + coin.getName());
        }
        // min_amount 单笔最小提现数量
        if (applyAmount.compareTo(coin.getMinAmount()) < 0) {
            log.error("提现数量不能小于单笔最小提现数量");
            //throw new GlobalDefaultException(20015);
            return Response.err(20015, "超出单笔提现下限" + format.format(coin.getMinAmount()) + coin.getName());
        }
        // base_amount 最小提现单位
        BigDecimal[] base = applyAmount.divideAndRemainder(coin.getBaseAmount());
        if (base[1].compareTo(BigDecimal.ZERO) > 0) {
            log.error("提现数量应为最小提现单位整数倍");
            //throw new GlobalDefaultException(20014);
            return Response.err(20014, "提现数量应为最小提现单位" + coin.getBaseAmount() + "整数倍");
        }

        //可用金额校验 USDT_OMNI
        Long coinId = applyWithdraw.getCoinId();
        if (coinId == 4L || coinId == 6L) {
            coinId = 2L;
        }
        Account account = this.queryByUserIdAndCoinId(user.getId(), coinId);
        if (account == null) {
            log.error("提币申请资金账户异常，userId:{}, coinId:{}", user.getId(), coinId);
            throw new AccountException("资金账户不存在");
        }
        if (account.getBalanceAmount().compareTo(applyWithdraw.getAmount()) < 0) {
            throw new AccountException("可用资金不足");
        }

        CoinWithdrawDTO coinWithdraw = new CoinWithdrawDTO();
        //用户提币地址列表
        UserWallet userWallet;
        String walletName = "";
        //提币收款地址
        String address;
        // 如果选择了地址
        if (applyWithdraw.getAddressId() != null && applyWithdraw.getAddressId() > 0L) {
            userWallet = userWalletService.selectById(applyWithdraw.getAddressId());
            if (userWallet == null || StringUtils.isEmpty(userWallet.getAddr())) {
                throw new GlobalDefaultException(50022);
            }
            walletName = userWallet.getName();
            address = userWallet.getAddr();
        } else {
            address = applyWithdraw.getAddress();
            // 自动保存
            userWallet = new UserWallet()
                    .setUserId(user.getId())
                    .setCoinId(applyWithdraw.getCoinId())
                    .setCoinName(coin.getName())
                    .setName("")
                    .setAddr(address)
                    .setSort(0)
                    .setStatus(BaseStatus.EFFECTIVE.getCode());
            //判断地址是否重复写入
            boolean exist = userWalletService.selectExist(user.getId(), applyWithdraw.getCoinId(), address);
            if (!exist) {
                userWalletService.insert(userWallet);
            }
        }
        // 提现实际转出金额
        if (applyWithdraw.getAmount().compareTo(coin.getMinFeeNum()) < 0) {
            log.error("提现数量需大于提现手续费");
            throw new GlobalDefaultException(50057);
        }
        //冻结提币金额
        baseMapper.lockAmount(account.getId(), applyWithdraw.getAmount());

        BigDecimal amount = applyWithdraw.getAmount().subtract(coin.getMinFeeNum());
        coinWithdraw.setUserId(user.getId())
                .setCoinId(coin.getId())
                .setCoinName(coin.getName())
                .setCoinType(coin.getType())
                .setWalletName(walletName)
                .setAddress(address)
                .setStep(1)
                .setStatus(CoinWithdrawStatus.PENDING.getCode())
                .setNum(applyWithdraw.getAmount())
                .setFee(coin.getMinFeeNum())
                .setMum(amount)
                .setCreated(new Date())
                .setLastUpdateTime(new Date());
        //调用wallet服务 写入提币交易
        Long orderId = coinWithdrawServiceClient.applyWithdraw(coinWithdraw);
        if (orderId == null || orderId == 0L) {
            log.error("提币申请失败");
            throw new GlobalDefaultException(50023);
        }
        try {
            //设置24提现总额 刷新KEY
            stringRedisTemplate.opsForValue().set(drawKey, dayMaxAmount.toString());
            long expireTime = DateUtil.getCurrentLeaveTime();
            stringRedisTemplate.expire(drawKey, expireTime, TimeUnit.MILLISECONDS);
        } catch (ParseException e) {
            log.error("获取当天过期时间异常");
        }

        //保存提币流水纪录
        AccountDetail accountDetail = new AccountDetail(
                user.getId(),
                coinId,
                account.getId(),
                account.getId(),
                orderId,
                AmountDirection.OUT.getType(),
                BusinessType.WITHDRAW.getCode(),
                applyWithdraw.getAmount(),
                "冻结"
        );
        try {
            accountDetailService.insert(accountDetail);
        } catch (Exception e) {
            //这里影响不大 不进行回滚 因为主要业务应执行完成
            log.warn("用户提币流水写入数据库失败 userId={} coinId={} amount={} fee={} orderId={} e={}",
                    user.getId(), applyWithdraw.getCoinId(), applyWithdraw.getAmount(), coin.getMinFeeNum(), orderId, e);
        }
        return Response.ok();
    }

    /**
     * 初始化资金账户
     *
     * @param userId 用户ID
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean syncAccount(Long userId) {
        EntityWrapper<Coin> wrapper = new EntityWrapper<>();
        // wrapper.notLike("type", "forex");
        List<Coin> coinList = coinService.selectList(wrapper);
        if (CollectionUtils.isEmpty(coinList)) {
            log.info("数据库coin表未配置币种信息 用户资金账户创建失败");
            return false;
        }
        for (Coin coin : coinList) {
            Boolean isMain = coin.getIsMain();
            if (isMain) {
                //查看是否存在 不存在则创建主键地址
                EntityWrapper<Account> wrap = new EntityWrapper<Account>();
                wrap.eq("coin_id", coin.getId());
                wrap.eq("user_id", userId);
                Account account = super.selectOne(wrap);
                if (account == null) {
                    account = new Account(userId, coin.getId(), BaseStatus.EFFECTIVE.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                            BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, "", 0L, new Date(), new Date());
                    this.insert(account);
                }
            } else {
                //查询主链地址是否存在 存在则使用主链地址 不存在则创建主链地址 然后使用主键地址
                EntityWrapper<Account> wrap = new EntityWrapper<Account>();
                wrap.eq("coin_id", coin.getMainCoinId());
                wrap.eq("user_id", userId);
                Account account = super.selectOne(wrap);
                if (account == null) {
                    //创建主链地址
                    account = new Account(userId, coin.getMainCoinId(), BaseStatus.EFFECTIVE.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                            BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, "", 0L, new Date(), new Date());
                    this.insert(account);

                    //从链使用主链地址
                    account = new Account(userId, coin.getId(), BaseStatus.EFFECTIVE.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                            BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, account.getRecAddr(), 0L, new Date(), new Date());
                    this.insert(account);
                } else {
                    account = new Account(userId, coin.getId(), BaseStatus.EFFECTIVE.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                            BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, account.getRecAddr(), 0L, new Date(), new Date());
                    this.insert(account);
                }
            }
        }
        EntityWrapper<Market> marketWrapper = new EntityWrapper<>();
        marketWrapper.eq("type", TradeAreaType.TC_TYPE.getCode());
        List<Market> marketList = marketService.selectList(marketWrapper);
        List<ForexAccount> forexAccounts = new ArrayList<>();
        for (Market market : marketList) {
            ForexAccount buyAccount = new ForexAccount(userId, market.getId(), market.getName(), OrderType.BUY.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, MarketStatus.USE.getValue(), new Date(), new Date());
            ForexAccount sellAccount = new ForexAccount(userId, market.getId(), market.getName(), OrderType.SELL.getCode(), BigDecimal.ZERO, BigDecimal.ZERO, MarketStatus.USE.getValue(), new Date(), new Date());
            forexAccounts.add(buyAccount);
            forexAccounts.add(sellAccount);
        }
        boolean addForexAccountResult = false;
        if (!CollectionUtils.isEmpty(forexAccounts)) {
            addForexAccountResult = forexAccountService.insertBatch(forexAccounts);
            log.info("用户外汇账户创建结果={}", addForexAccountResult);
        } else {
            log.warn("forexAccounts为空 请设置数据库market表");
        }
        if (addForexAccountResult) {
            return true;
        } else {
            throw new GlobalDefaultException(2007);
        }
    }

    /**
     * 资金转移
     *
     * @param fromUserId   转出账户
     * @param toUserId     转入账户
     * @param coinId       币种
     * @param amount       金额
     * @param businessType 业务类型
     * @param orderId      关联订单号
     */
    @Override
    public boolean transferAmount(Long fromUserId, Long toUserId, Long coinId, BigDecimal amount, BusinessType businessType, Long orderId) {
        Account fromAccount = this.queryByUserIdAndCoinId(fromUserId, coinId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new AccountException("资金账户异常");
        }
        Account toAccount = this.queryByUserIdAndCoinId(toUserId, coinId);
        if (toAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", toUserId, coinId);
            throw new AccountException("资金账户异常");
        }
        // 扣减资金
        if (baseMapper.subtractAmount(fromAccount.getId(), amount) > 0) {
            // 保存流水
            AccountDetail fromAccountDetail = new AccountDetail(fromUserId,
                    coinId,
                    fromAccount.getId(),
                    toAccount.getId(),
                    orderId,
                    AmountDirection.OUT.getType(),
                    businessType.getCode(),
                    amount,
                    businessType.getDesc());
            accountDetailService.insert(fromAccountDetail);
            // 增加资金
            if (baseMapper.addAmount(toAccount.getId(), amount) > 0) {
                // 保存流水
                AccountDetail toAccountDetail = new AccountDetail(toUserId,
                        coinId,
                        toAccount.getId(),
                        fromAccount.getId(),
                        orderId,
                        AmountDirection.INCOME.getType(),
                        businessType.getCode(),
                        amount,
                        businessType.getDesc());
                accountDetailService.insert(toAccountDetail);
                return true;
            }
        }
        log.error("资金划转，orderId:{}, fromUserId:{}, toUserId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, fromUserId, toUserId, coinId, amount, businessType.getCode());
        throw new AccountException("资金划转失败");
    }

    @Override
    public int addAmountById(Long accountId, BigDecimal amount) {
        return baseMapper.addAmount(accountId, amount);
    }

    @Override
    public int subAmountById(Long accountId, BigDecimal amount) {
        return baseMapper.subtractAmount(accountId, amount);
    }

    /**
     * 统计账户资产
     *
     * @param userId 用户ID
     * @return
     */
    /*@Override
    public Map<String, Object> countAssets(Long userId) throws ExecutionException {
        Map<String, Object> map = new HashMap<>();
        // 总资产
        BigDecimal total = BigDecimal.ZERO;
        // 获取平台币
        EntityWrapper<Config> wrapper = new EntityWrapper<>();
        wrapper.eq("type", CONFIG_TYPE_SYSTEM).eq("code", "PLATFORM_COIN_ID");
        Config config = configService.selectOne(wrapper);
        if (config == null) {
            log.error("没有配置平台资产统计币种");
            return map;
        }
        // 获取资产统计币种的ID
        long baseCoinId = Long.parseLong(config.getValue());
        List<List<Market>> marketList = this.getAllMarket(baseCoinId);
        if (CollectionUtils.isEmpty(marketList)) {
            return map;
        }
        List<Market> usdtMarketList = marketList.get(0);
        List<Market> otherMarketList = new ArrayList<>();
        if (marketList.size() == 2) {
            otherMarketList = marketList.get(1);
        }
        // 资金账户列表
        List<AccountDTO> accounts = new ArrayList<>();
        EntityWrapper<Account> ew = new EntityWrapper<>();
        ew.eq("user_id", userId);
        List<Account> accountList = baseMapper.selectList(ew);
        for (Account account : accountList) {
            AccountDTO accountDTO = AccountDTOMapper.INSTANCE.from(account);
            Long coinId = account.getCoinId();
            BigDecimal volume = account.getBalanceAmount().add(account.getFreezeAmount());
            // 币种信息
            Coin coin = coinService.queryById(account.getCoinId());
            if (coin == null || coin.getStatus() != BaseStatus.EFFECTIVE.getCode()) {
                // 过滤无效币种
                continue;
            }
            accountDTO.setCoinImgUrl(coin.getImg())             // 币种图片路径
                    .setCoinName(coin.getName())                // 币种名称
                    .setFeeRate(new BigDecimal(coin.getRate())
                            .setScale(3, BigDecimal.ROUND_HALF_UP))    // 提现手续费
                    .setRechargeFlag(coin.getRechargeFlag())    // 充值开关
                    .setWithdrawFlag(coin.getWithdrawFlag())    // 提现开关
                    .setCoinType(coin.getType())                // 币种类型
                    .setMinFeeNum(coin.getMinFeeNum().stripTrailingZeros())          // 提币手续费
                    .setCarryingAmount(accountDTO.getBalanceAmount()
                            .add(accountDTO.getFreezeAmount())); //账面余额
            accounts.add(accountDTO);
            // 计算总资产
            if (volume.compareTo(BigDecimal.ZERO) <= 0) {
                // 资产为0
                continue;
            }
            // TODO 最新成交价(USDT)
            BigDecimal currentPrice;
            if (account.getCoinId().equals(baseCoinId)) {
                currentPrice = BigDecimal.ONE;
            } else {
                // 当前币种对USDT交易对
                Market market = this.getMarket(baseCoinId, coinId, usdtMarketList);
                if (market == null) {
                    // 当前币种对非USDT交易对
                    market = this.getMarket(coinId, otherMarketList);
                    if (market == null) {
                        continue;
                    }
                    TradeMarketDTO tradeMarket = marketService.TRADE_MARKET_CACHE_INSTANCE().get(market.getId());
                    currentPrice = tradeMarket.getPrice();
                    market = this.getMarket(market.getBuyCoinId(), otherMarketList);
                    if (market == null) {
                        continue;
                    }
                    tradeMarket = marketService.TRADE_MARKET_CACHE_INSTANCE().get(market.getId());
                    currentPrice = currentPrice.multiply(tradeMarket.getPrice());
                } else {
                    TradeMarketDTO tradeMarket = marketService.TRADE_MARKET_CACHE_INSTANCE().get(market.getId());
                    currentPrice = tradeMarket.getPrice();
                }
            }
            // 当前资金账户总资产（单位：USDT） = 币总量 * 币价格（单位：USDT）
            BigDecimal amount = volume.multiply(currentPrice);
            total = total.add(amount);
        }
        // CNY/USDT兑换比例
        BigDecimal cny2usdtRate = new BigDecimal(6.42);
        // 获取美元兑换率
        wrapper = new EntityWrapper<>();
        wrapper.eq("type", CONFIG_TYPE_SYSTEM).eq("code", CONFIG_CNY2USDT);
        config = configService.selectOne(wrapper);
        if (config != null) {
            cny2usdtRate = new BigDecimal(config.getValue());
        }
        // 计算总资产美元价格
        // 总资产美元
        BigDecimal totalCny = total.multiply(cny2usdtRate);
        total = total.setScale(2, BigDecimal.ROUND_UP);       // 保留两位小数
        totalCny = totalCny.setScale(2, BigDecimal.ROUND_UP);    // 保留两位小数
        map.put("amount", totalCny.setScale(2, RoundingMode.DOWN));
        map.put("amountUs", total.setScale(2, RoundingMode.DOWN));
        map.put("assertList", accounts);
        return map;
    }*/
    @Override
    public Map<String, Object> countAssets(Long userId) {
        Map<String, Object> map = new HashMap<>();

        Wrapper<Account> wrap = new EntityWrapper<>();
        wrap.eq("user_id", userId);
        List<Account> all = super.selectList(wrap);

        // 总资产 单位：USDT
        BigDecimal total = BigDecimal.ZERO;

        // 资金账户列表
        List<AccountDTO> accounts = new ArrayList<>();
        for (Account account : all) {
            AccountDTO accountDTO = AccountDTOMapper.INSTANCE.from(account);
            BigDecimal volume = account.getBalanceAmount().add(account.getFreezeAmount());
            // 币种信息
            log.info("获取币种信息start... coinId={} userId={}", account.getCoinId(), account.getUserId());
            Coin coin = coinService.queryById(account.getCoinId());
            log.info("获取币种信息end...");
            if (coin == null || coin.getStatus() != BaseStatus.EFFECTIVE.getCode()) {
                // 过滤无效币种
                continue;
            }
            accountDTO.setCoinImgUrl(coin.getImg())             // 币种图片路径
                    .setCoinName(coin.getName())                // 币种名称
                    .setFeeRate(new BigDecimal(coin.getRate().toString()).setScale(4, BigDecimal.ROUND_HALF_UP))    // 提现手续费
                    .setRechargeFlag(coin.getRechargeFlag())    // 充值开关
                    .setWithdrawFlag(coin.getWithdrawFlag())    // 提现开关
                    .setCoinType(coin.getType())                // 币种类型
                    .setMinFeeNum(coin.getMinFeeNum().stripTrailingZeros())          // 提币手续费
                    .setCarryingAmount(volume); //账面余额
            if (volume.compareTo(BigDecimal.ZERO) > 0) {
                String coinName = coin.getName();
                if (!coinName.startsWith("USDT")) {
                    coinName = coinName.toLowerCase() + "usdt";
                    //调用火币API获取数据
                    String redisCurrentPrice = "coinPriceUsdt:" + coinName;
                    BigDecimal priceUsdt = (BigDecimal) redisTemplate.opsForValue().get(redisCurrentPrice);
                    if (priceUsdt != null) {
                        total = total.add(volume.multiply(priceUsdt));
                    } else {
                        try {
                            MarketClient marketClient = MarketClient.create(new HuobiOptions());
                            List<Candlestick> list = marketClient.getCandlestick(CandlestickRequest.builder()
                                    .size(1)
                                    .interval(CandlestickIntervalEnum.DAY1)
                                    .symbol(coinName)
                                    .build());
                            Candlestick candlestick = list.get(0);
                            priceUsdt = candlestick.getClose();
                            redisTemplate.opsForValue().set(redisCurrentPrice, priceUsdt, 200L, TimeUnit.MILLISECONDS);
                            total = total.add(volume.multiply(priceUsdt));
                        } catch (Exception e) {
                            log.warn("调用火币API获取币种价格失败 symbol={}", coinName);
                            total = total.add(volume);
                            priceUsdt = BigDecimal.ONE;
                        }
                    }
                    accountDTO.setCurrentPrice(priceUsdt);
                    accountDTO.setTotalUsdt(volume.multiply(priceUsdt));
                } else {
                    total = total.add(volume);
                    accountDTO.setCurrentPrice(BigDecimal.ONE);
                    accountDTO.setTotalUsdt(volume);
                }
            }
            accounts.add(accountDTO);
        }
        //TODO USDT兑换人民币 汇率 暂时写死 6.42
        map.put("amount", total.multiply(new BigDecimal("6.42")).setScale(2, RoundingMode.DOWN));
        map.put("amountUs", total.setScale(2, RoundingMode.DOWN));
        map.put("assertList", accounts);
        return map;
    }

    /**
     * 获取对usdt交易对
     *
     * @param buyCoinId  报价币种
     * @param sellCoinId 卖出币种
     * @param markets    交易对集合
     * @return
     */
    private Market getMarket(Long buyCoinId, Long sellCoinId, List<Market> markets) {
        for (Market market : markets) {
            if (market.getBuyCoinId().equals(buyCoinId)
                    && market.getSellCoinId().equals(sellCoinId)) {
                return market;
            }
        }
        return null;
    }

    /**
     * 获取对usdt交易对
     *
     * @param sellCoinId 卖出币种
     * @param markets    交易对集合
     * @return
     */
    private Market getMarket(Long sellCoinId, List<Market> markets) {
        for (Market market : markets) {
            if (market.getSellCoinId().equals(sellCoinId)) {
                return market;
            }
        }
        return null;
    }

    /**
     * 获取所有的交易对, 去除各个区之间的重叠
     *
     * @return
     */
    public List<List<Market>> getAllMarket(Long baseCoinId) {
        List<List<Market>> marketList = new ArrayList<>();
        List<Market> usdtMarketList = new ArrayList<>();
        List<Market> otherMarketList = new ArrayList<>();
        // 获取交易区域 得到市场buyId
        EntityWrapper<Market> wrapper = new EntityWrapper<>();
        wrapper.eq("status", 1)
                .orderBy("trade_area_id", true)
                .orderBy("sort", true);
        List<Market> markets = marketService.selectList(wrapper);
        for (Market market : markets) {
            if (market.getBuyCoinId().equals(baseCoinId)) {
                usdtMarketList.add(market);
            } else {
                otherMarketList.add(market);
            }
        }
        marketList.add(usdtMarketList);
        marketList.add(otherMarketList);
        return marketList;
    }

}
